#!/usr/bin/env python
# -*- coding: utf-8 -*-

import os
import sys
import threading
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
from tkinter.scrolledtext import ScrolledText
import requests
from PIL import Image, ImageTk
from io import BytesIO
from music_api import MusicAPI
import re

class DownloaderApp:
    def __init__(self, root):
        self.root = root
        self.root.title("音乐下载器")
        self.root.geometry("900x650")
        self.root.resizable(True, True)
        
        self.api = MusicAPI()
        self.search_results = []
        self.current_cover_image = None  # 用于保持引用
        self.setup_gui()
    
    def setup_gui(self):
        # 创建标签页
        self.notebook = ttk.Notebook(self.root)
        self.notebook.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
        
        # 音乐搜索标签页
        self.music_tab = ttk.Frame(self.notebook)
        self.notebook.add(self.music_tab, text="音乐搜索")
        
        # YouTube下载标签页
        self.youtube_tab = ttk.Frame(self.notebook)
        self.notebook.add(self.youtube_tab, text="YouTube下载")
        
        # 设置音乐搜索标签页
        self.setup_music_tab()
        
        # 设置YouTube下载标签页
        self.setup_youtube_tab()
        
        # 底部状态栏
        self.status_frame = ttk.Frame(self.root)
        self.status_frame.pack(fill=tk.X, padx=10, pady=(0, 10))
        
        self.progress = ttk.Progressbar(self.status_frame, orient=tk.HORIZONTAL, length=100, mode='determinate')
        self.progress.pack(fill=tk.X, side=tk.TOP, padx=5, pady=5)
        self.progress.pack_forget()  # 初始隐藏
        
        self.status_label = ttk.Label(self.status_frame, text="准备就绪")
        self.status_label.pack(fill=tk.X, side=tk.BOTTOM, padx=5)
    
    def setup_music_tab(self):
        # 搜索区域
        search_frame = ttk.Frame(self.music_tab)
        search_frame.pack(fill=tk.X, padx=10, pady=10)
        
        ttk.Label(search_frame, text="搜索音乐:").grid(row=0, column=0, padx=5, pady=5, sticky=tk.W)
        
        self.search_entry = ttk.Entry(search_frame, width=30)
        self.search_entry.grid(row=0, column=1, padx=5, pady=5, sticky=tk.W+tk.E)
        self.search_entry.bind("<Return>", lambda e: self.search_music())
        
        ttk.Label(search_frame, text="音乐源:").grid(row=0, column=2, padx=5, pady=5, sticky=tk.W)
        
        self.source_var = tk.StringVar()
        self.source_combo = ttk.Combobox(search_frame, textvariable=self.source_var, width=15)
        self.source_combo['values'] = ('网易云音乐', 'QQ音乐')
        self.source_combo.current(0)
        self.source_combo.grid(row=0, column=3, padx=5, pady=5, sticky=tk.W)
        
        self.search_button = ttk.Button(search_frame, text="搜索", command=self.search_music)
        self.search_button.grid(row=0, column=4, padx=5, pady=5, sticky=tk.E)
        
        search_frame.columnconfigure(1, weight=1)
        
        # 结果区域 - 分为左侧结果表和右侧详情区
        result_frame = ttk.Frame(self.music_tab)
        result_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
        
        # 左侧结果表
        table_frame = ttk.Frame(result_frame)
        table_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        
        # 创建表格
        columns = ('歌曲名', '歌手', '专辑', '时长', '大小')
        self.tree = ttk.Treeview(table_frame, columns=columns, show='headings', selectmode='browse')
        
        # 设置列标题
        for col in columns:
            self.tree.heading(col, text=col)
            self.tree.column(col, width=100, anchor='center')
        
        self.tree.column('歌曲名', width=150, anchor='w')
        self.tree.column('歌手', width=100, anchor='w')
        self.tree.column('专辑', width=120, anchor='w')
        
        # 添加滚动条
        scrollbar = ttk.Scrollbar(table_frame, orient=tk.VERTICAL, command=self.tree.yview)
        self.tree.configure(yscrollcommand=scrollbar.set)
        
        # 绑定选择事件
        self.tree.bind('<<TreeviewSelect>>', self.on_tree_select)
        
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        self.tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        
        # 右侧详情区
        details_frame = ttk.Frame(result_frame, width=200)
        details_frame.pack(side=tk.RIGHT, fill=tk.Y, padx=(10, 0))
        
        # 专辑封面
        cover_frame = ttk.LabelFrame(details_frame, text="专辑封面")
        cover_frame.pack(fill=tk.X, pady=5)
        
        self.cover_label = ttk.Label(cover_frame)
        self.cover_label.pack(padx=10, pady=10)
        
        # 歌曲信息
        info_frame = ttk.LabelFrame(details_frame, text="歌曲信息")
        info_frame.pack(fill=tk.X, pady=5)
        
        self.song_label = ttk.Label(info_frame, text="歌曲: -")
        self.song_label.pack(anchor=tk.W, padx=10, pady=2)
        
        self.artist_label = ttk.Label(info_frame, text="歌手: -")
        self.artist_label.pack(anchor=tk.W, padx=10, pady=2)
        
        self.album_label = ttk.Label(info_frame, text="专辑: -")
        self.album_label.pack(anchor=tk.W, padx=10, pady=2)
        
        # 下载设置
        download_frame = ttk.LabelFrame(details_frame, text="下载设置")
        download_frame.pack(fill=tk.X, pady=5)
        
        ttk.Label(download_frame, text="格式:").pack(anchor=tk.W, padx=10, pady=2)
        
        self.format_var = tk.StringVar(value="MP3")
        format_frame = ttk.Frame(download_frame)
        format_frame.pack(fill=tk.X, padx=10, pady=2)
        
        ttk.Radiobutton(format_frame, text="MP3", variable=self.format_var, value="MP3").pack(side=tk.LEFT)
        ttk.Radiobutton(format_frame, text="FLAC", variable=self.format_var, value="FLAC").pack(side=tk.LEFT, padx=(10, 0))
        
        ttk.Label(download_frame, text="保存位置:").pack(anchor=tk.W, padx=10, pady=2)
        
        path_frame = ttk.Frame(download_frame)
        path_frame.pack(fill=tk.X, padx=10, pady=2)
        
        self.path_var = tk.StringVar(value=os.path.join(os.path.expanduser('~'), 'Downloads'))
        path_entry = ttk.Entry(path_frame, textvariable=self.path_var)
        path_entry.pack(side=tk.LEFT, fill=tk.X, expand=True)
        
        browse_button = ttk.Button(path_frame, text="浏览...", command=self.browse_location)
        browse_button.pack(side=tk.RIGHT, padx=(5, 0))
        
        download_button = ttk.Button(download_frame, text="下载选中歌曲", command=self.download_selected)
        download_button.pack(fill=tk.X, padx=10, pady=10)
    
    def setup_youtube_tab(self):
        # YouTube下载标签页内容
        frame = ttk.Frame(self.youtube_tab, padding=10)
        frame.pack(fill=tk.BOTH, expand=True)
        
        # URL输入
        ttk.Label(frame, text="YouTube URL:").grid(row=0, column=0, sticky=tk.W, pady=10)
        self.yt_url_var = tk.StringVar()
        ttk.Entry(frame, textvariable=self.yt_url_var, width=50).grid(row=0, column=1, sticky=tk.EW, pady=10, padx=5)
        
        # 文件名输入
        ttk.Label(frame, text="保存文件名:").grid(row=1, column=0, sticky=tk.W, pady=10)
        self.yt_filename_var = tk.StringVar()
        ttk.Entry(frame, textvariable=self.yt_filename_var, width=50).grid(row=1, column=1, sticky=tk.EW, pady=10, padx=5)
        
        # 格式选择
        ttk.Label(frame, text="文件格式:").grid(row=2, column=0, sticky=tk.W, pady=10)
        format_frame = ttk.Frame(frame)
        format_frame.grid(row=2, column=1, sticky=tk.W, pady=10)
        
        self.yt_format_var = tk.StringVar(value="mp3")
        ttk.Radiobutton(format_frame, text="MP3", variable=self.yt_format_var, value="mp3").pack(side=tk.LEFT)
        ttk.Radiobutton(format_frame, text="FLAC", variable=self.yt_format_var, value="flac").pack(side=tk.LEFT, padx=(10, 0))
        
        # 保存位置
        ttk.Label(frame, text="保存位置:").grid(row=3, column=0, sticky=tk.W, pady=10)
        loc_frame = ttk.Frame(frame)
        loc_frame.grid(row=3, column=1, sticky=tk.EW, pady=10)
        
        self.yt_path_var = tk.StringVar(value=os.path.join(os.path.expanduser('~'), 'Downloads'))
        ttk.Entry(loc_frame, textvariable=self.yt_path_var).pack(side=tk.LEFT, fill=tk.X, expand=True)
        ttk.Button(loc_frame, text="浏览...", command=self.browse_yt_location).pack(side=tk.RIGHT, padx=(5, 0))
        
        # 下载按钮
        ttk.Button(frame, text="从YouTube下载", command=self.download_from_youtube).grid(row=4, column=0, columnspan=2, pady=20)
        
        # 设置列权重
        frame.columnconfigure(1, weight=1)
    
    def search_music(self):
        """搜索音乐功能"""
        query = self.search_entry.get().strip()
        if not query:
            messagebox.showwarning("警告", "请输入搜索关键词")
            return
        
        # 显示搜索状态
        self.status_label.config(text="正在搜索...")
        self.root.update()
        
        # 获取音乐源
        source_map = {
            '网易云音乐': 'netease',
            'QQ音乐': 'qq'
        }
        source = source_map.get(self.source_var.get(), 'netease')
        
        # 清空现有结果
        for item in self.tree.get_children():
            self.tree.delete(item)
        
        # 清空详情显示
        self.clear_song_details()
        
        # 使用线程执行搜索，避免界面卡顿
        def search_thread():
            try:
                # 调用API搜索
                results = self.api.search_music(query, source=source)
                self.search_results = results
                
                # 在UI线程中更新界面
                self.root.after(0, lambda: self.update_search_results(results))
            except Exception as e:
                self.root.after(0, lambda: self.show_error(f"搜索出错: {str(e)}"))
        
        threading.Thread(target=search_thread).start()
    
    def update_search_results(self, results):
        """更新搜索结果列表"""
        # 更新表格
        for i, result in enumerate(results):
            # 选择当前格式的大小显示
            size_key = 'size_mp3' if self.format_var.get() == 'MP3' else 'size_flac'
            
            # 处理歌曲名称 - 为VIP歌曲添加标记
            name_display = result['name']
            if result.get('is_vip', False):
                name_display = f"{result['name']} [VIP]"
                
            values = (
                name_display,
                result['artist'],
                result['album'],
                result['duration'],
                result[size_key]
            )
            self.tree.insert('', tk.END, values=values, iid=str(i))
            
            # 为VIP歌曲设置特殊颜色
            if result.get('is_vip', False):
                self.tree.item(str(i), tags=('vip',))
        
        # 配置VIP标签的样式
        self.tree.tag_configure('vip', foreground='#E91E63')
        
        # 更新状态
        self.status_label.config(text=f"找到 {len(results)} 首歌曲")
    
    def on_tree_select(self, event):
        """处理列表选择事件"""
        selection = self.tree.selection()
        if not selection:
            return
        
        index = int(selection[0])
        if index >= len(self.search_results):
            return
        
        song_data = self.search_results[index]
        
        # 更新歌曲信息
        self.song_label.config(text=f"歌曲: {song_data['name']}")
        self.artist_label.config(text=f"歌手: {song_data['artist']}")
        self.album_label.config(text=f"专辑: {song_data['album']}")
        
        # 根据VIP状态更新UI
        if song_data.get('is_vip', False):
            # 为VIP歌曲添加特殊标记
            self.song_label.config(text=f"歌曲: {song_data['name']} [VIP]")
        
        # 加载封面图片
        if 'pic_url' in song_data and song_data['pic_url']:
            self.load_cover_image(song_data['pic_url'])
        else:
            self.clear_cover_image()
    
    def load_cover_image(self, url):
        """加载专辑封面"""
        def download_image():
            try:
                # 处理URL格式
                if not url.startswith('http'):
                    url = f"https:{url}" if url.startswith('//') else f"https://{url}"
                
                # 确保URL带有清晰度参数
                if '?' not in url and 'music.163.com' in url:
                    url += '?param=300y300'
                
                # 打印URL作为调试信息
                print(f"加载封面图片URL: {url}")
                
                # 选择适当的请求头
                headers = {}
                if 'music.163.com' in url:
                    headers = {
                        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36',
                        'Referer': 'https://music.163.com/',
                        'Accept': 'image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8'
                    }
                elif 'qq.com' in url or 'gtimg.cn' in url:
                    headers = {
                        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36',
                        'Referer': 'https://y.qq.com/',
                        'Accept': 'image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8'
                    }
                else:
                    headers = {
                        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36',
                        'Accept': 'image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8'
                    }
                
                # 使用代码自动添加超时和重试机制
                session = requests.Session()
                adapter = requests.adapters.HTTPAdapter(max_retries=3)
                session.mount('http://', adapter)
                session.mount('https://', adapter)
                
                response = session.get(url, headers=headers, timeout=10, stream=True)
                
                if response.status_code == 200:
                    # 验证内容是否是图片
                    content_type = response.headers.get('Content-Type', '')
                    if not ('image' in content_type):
                        print(f"返回的内容不是图片: {content_type}")
                        self.root.after(0, self.clear_cover_image)
                        return
                        
                    # 使用PIL处理图片
                    img = Image.open(BytesIO(response.content))
                    img = img.resize((150, 150), Image.LANCZOS)
                    photo = ImageTk.PhotoImage(img)
                    
                    # 在主线程中更新UI
                    self.root.after(0, lambda: self.update_cover_image(photo))
                else:
                    print(f"封面图片请求失败: 状态码 {response.status_code}")
                    
                    # 尝试备用URL (适用于网易云)
                    if 'music.163.com' in url and 'param=' in url:
                        # 如果是网易云且原URL加了参数，尝试不加参数
                        new_url = url.split('?')[0]
                        print(f"尝试备用图片URL: {new_url}")
                        
                        backup_response = session.get(new_url, headers=headers, timeout=10)
                        if backup_response.status_code == 200:
                            img = Image.open(BytesIO(backup_response.content))
                            img = img.resize((150, 150), Image.LANCZOS)
                            photo = ImageTk.PhotoImage(img)
                            self.root.after(0, lambda: self.update_cover_image(photo))
                            return
                    
                    self.root.after(0, self.clear_cover_image)
            except Exception as e:
                print(f"加载封面图失败: {str(e)}")
                self.root.after(0, self.clear_cover_image)
        
        # 如果URL为空，直接清空
        if not url:
            self.clear_cover_image()
            return
        
        threading.Thread(target=download_image).start()
    
    def update_cover_image(self, photo):
        """更新封面图片"""
        self.current_cover_image = photo  # 保持引用，防止垃圾回收
        self.cover_label.config(image=photo)
    
    def clear_cover_image(self):
        """清空封面图片"""
        self.cover_label.config(image='')
        self.cover_label.config(text="无封面")
        self.current_cover_image = None
    
    def clear_song_details(self):
        """清空歌曲详情"""
        self.song_label.config(text="歌曲: -")
        self.artist_label.config(text="歌手: -")
        self.album_label.config(text="专辑: -")
        self.clear_cover_image()
    
    def browse_location(self):
        """选择下载保存位置"""
        directory = filedialog.askdirectory(initialdir=self.path_var.get())
        if directory:
            self.path_var.set(directory)
    
    def browse_yt_location(self):
        """选择YouTube下载保存位置"""
        directory = filedialog.askdirectory(initialdir=self.yt_path_var.get())
        if directory:
            self.yt_path_var.set(directory)
    
    def download_selected(self):
        """下载选中的音乐"""
        selection = self.tree.selection()
        if not selection:
            messagebox.showwarning("警告", "请选择要下载的歌曲")
            return
        
        index = int(selection[0])
        if index >= len(self.search_results):
            return
        
        song_data = self.search_results[index]
        current_format = self.format_var.get().lower()
        url_key = f'url_{current_format}'
        extension = f'.{current_format}'
        
        # 检查歌曲是否为VIP
        is_vip = song_data.get('is_vip', False)
        if is_vip:
            # 提示用户这是VIP歌曲，可能无法下载
            warning_message = (
                f"您选择的歌曲 '{song_data['name']}' 是VIP专属歌曲，"
                "程序将使用特殊方法尝试下载，但不保证成功。\n\n"
                "是否继续？"
            )
            if not messagebox.askyesno("VIP歌曲提示", warning_message):
                return
        
        # 获取音乐源
        source_map = {
            '网易云音乐': 'netease',
            'QQ音乐': 'qq'
        }
        source = source_map.get(self.source_var.get(), 'netease')
        
        # 创建保存路径
        directory = self.path_var.get()
        if not os.path.isdir(directory):
            try:
                os.makedirs(directory)
            except Exception as e:
                messagebox.showerror("错误", f"无法创建目录: {str(e)}")
                return
        
        # 处理文件名中的非法字符
        filename = f"{song_data['artist']} - {song_data['name']}"
        filename = re.sub(r'[\\/*?:"<>|]', '_', filename) + extension
        save_path = os.path.join(directory, filename)
        
        # 准备元数据
        metadata = {
            'title': song_data['name'],
            'artist': song_data['artist'],
            'album': song_data['album'],
            'is_vip': is_vip,  # 传递VIP状态
            'source': source    # 传递音乐源
        }
        
        # 添加封面URL（如果有）
        if 'pic_url' in song_data and song_data['pic_url']:
            metadata['cover_url'] = song_data['pic_url']
        
        # 获取下载链接
        if url_key in song_data and song_data[url_key]:
            download_url = song_data[url_key]
        else:
            # 如果没有直接链接，尝试通过API获取
            download_url = self.api.get_song_url(
                song_data['id'], 
                format_type=current_format,
                source=source
            )
        
        # 显示进度条
        self.progress.pack(fill=tk.X, side=tk.TOP, padx=5, pady=5)
        self.progress['value'] = 0
        status_text = f"正在下载 {song_data['name']}..."
        if is_vip:
            status_text = f"正在下载VIP歌曲 {song_data['name']}..."
        self.status_label.config(text=status_text)
        self.root.update()
        
        # 在线程中执行下载
        def download_thread():
            try:
                # 模拟进度条更新
                for i in range(1, 10):
                    self.root.after(300 * i, lambda v=i*10: self.progress.config(value=v))
                
                success, message = self.api.download_with_metadata(
                    download_url, 
                    save_path, 
                    metadata
                )
                
                # 在UI线程中更新结果
                self.root.after(0, lambda: self.download_complete(success, message))
            except Exception as e:
                self.root.after(0, lambda: self.download_complete(False, f"下载失败: {str(e)}"))
        
        threading.Thread(target=download_thread).start()
    
    def download_from_youtube(self):
        """从YouTube下载音乐"""
        url = self.yt_url_var.get().strip()
        if not url:
            messagebox.showwarning("警告", "请输入YouTube视频URL")
            return
        
        filename = self.yt_filename_var.get().strip()
        if not filename:
            messagebox.showwarning("警告", "请输入保存的文件名")
            return
        
        format_type = self.yt_format_var.get()
        directory = self.yt_path_var.get()
        
        if not os.path.isdir(directory):
            try:
                os.makedirs(directory)
            except Exception as e:
                messagebox.showerror("错误", f"无法创建目录: {str(e)}")
                return
        
        # 显示进度条
        self.progress.pack(fill=tk.X, side=tk.TOP, padx=5, pady=5)
        self.progress['value'] = 0
        self.status_label.config(text=f"正在从YouTube下载 {filename}...")
        self.root.update()
        
        # 在线程中执行下载
        def download_thread():
            try:
                # 模拟进度
                self.root.after(0, lambda: self.progress.config(value=10))
                
                success, message = self.api.download_from_youtube(
                    url,
                    directory,
                    filename,
                    format_type
                )
                
                self.root.after(0, lambda: self.progress.config(value=100))
                self.root.after(0, lambda: self.download_complete(success, message))
            except Exception as e:
                self.root.after(0, lambda: self.download_complete(False, f"下载失败: {str(e)}"))
        
        threading.Thread(target=download_thread).start()
    
    def download_complete(self, success, message):
        """下载完成处理"""
        self.progress.pack_forget()
        self.status_label.config(text=message)
        
        if success:
            messagebox.showinfo("成功", message)
        else:
            messagebox.showwarning("警告", message)
    
    def show_error(self, message):
        """显示错误消息"""
        self.status_label.config(text="发生错误")
        messagebox.showerror("错误", message)

if __name__ == "__main__":
    root = tk.Tk()
    app = DownloaderApp(root)
    root.mainloop() 